#include <sys/time.h>
#include <unistd.h>
#include <iostream>
#include "Wrappers/Pthread/Mutex.hpp"
#include "Wrappers/Pthread/Thread.hpp"
#include "Wrappers/Pthread/ThreadException.hpp"
#include "Wrappers/Pthread/Tests/Thread_tests.hpp"

namespace wrappers {
namespace pthread {

Thread_tests::Thread_tests()
    : mTestRunner("Threads' tests")
{
  mTestRunner.AddTestCase(UTESTS_MAKE_CASE_DATA(Thread_tests::Creation));
  mTestRunner.AddTestCase(UTESTS_MAKE_CASE_DATA(Thread_tests::JoinDetach));
  mTestRunner.AddTestCase(UTESTS_MAKE_CASE_DATA(Thread_tests::Cancelling));
  mTestRunner.Run(true);
}

Thread_tests::~Thread_tests()
{

}

UTESTS_START_TEST(Thread_tests::Creation)
  {
    Mutex mutex;
    Thread thread(ThreadActionMutexLock, &mutex);
    usleep(1000);
    if (mutex.TryLock())
      UTESTS_FAIL("TryLock succeeded but should have failed");
    bool exceptionRaised = false;
    try {
      thread.Run();
    }
    catch (ThreadException &e) {
      exceptionRaised = true;
    }
    UTESTS_FAIL_UNLESS(exceptionRaised, "Able to run a terminated thread");
  }
  UTESTS_END_TEST

UTESTS_START_TEST(Thread_tests::JoinDetach)
  {
    Thread thread(ThreadActionTimed);
    struct timeval beforeJoin, afterJoin;
    gettimeofday(&beforeJoin, NULL);
    thread.Join();
    gettimeofday(&afterJoin, NULL);
    UTESTS_FAIL_UNLESS(afterJoin.tv_usec - beforeJoin.tv_usec > 1000,
	"Join failed");
    Thread thread2(ThreadActionTimed);
    gettimeofday(&beforeJoin, NULL);
    thread2.Detach();
    gettimeofday(&afterJoin, NULL);
    UTESTS_FAIL_IF(afterJoin.tv_usec - beforeJoin.tv_usec > 1000,
	"Detach failed");
    if (thread2.Join()) {
      UTESTS_FAIL("Able to join a detached thread");
    }
  }
  UTESTS_END_TEST

UTESTS_START_TEST(Thread_tests::Cancelling)
  {
    Thread thread(ThreadActionTimed);
    thread.Cancel();
    if (thread.Join()) {
      UTESTS_FAIL("Able to join a cancelled thread");
    }
  }
  UTESTS_END_TEST

void *Thread_tests::ThreadActionMutexLock(void *args) {
  Mutex *mutex = (Mutex *) args;

  mutex->Lock();
  return NULL;
}

void *Thread_tests::ThreadActionTimed(void *args) {
  (void) args;
  usleep(2000);
  return NULL;
}

} /* namespace pthread */
} /* namespace wrappers */
